P0 KV-Server
Part 0
In this Key-Value Database Server Project, the following functions will be implemented:
- With non-thread-safe function
Put()
,Get()
,Delete()
,Update()
for the database, the server must manage and interact with its clients concurrently using Goroutines and channels. Multiple clients should be able to connect/disconnect to the server simultaneously.
- When the server reads a
Get()
request message from a client, it should respond with value
- The server must implement a
CountActive()
function that returns the number of clients that are currently connected to it.
- The server must implement a
CountDropped()
function that returns the number of clients that have been disconnected from and thus dropped from the server.
- The server must be responsive to slow-reading clients. To better understand what this means, consider a scenario in which a client does not call Read for an extended period of time. If during this time the server continues to write messages to the client’s TCP connection, eventually the TCP connections output buffer will reach maximum capacity and subsequent calls to Write made by the server will block. To handle these cases, your server should keep a queue of at most 500 outgoing messages to be written to the client at a later time. Messages sent to a slow-reading client whose outgoing message buffer has reached the maximum capacity of 500 should simply be dropped. If the slow-reading client starts reading again in the future, the server should ensure that any buffered messages in its queue are written back to the client (hint: use a buffered channel to implement this properly).
Part 1 Components
The keyValueServer
is the struct that act as a server and control the concurrent access of data from multiple clients.
type keyValueServer struct {
listener net.Listener
queries chan *Query
connections chan net.Conn
clients []*Client
db kvstore.KVStore
count_active chan int // Channel to receive active client count
close_signal chan struct{} // Channel to signal the server shutdown
closed bool
}
The listener is the socket that listens the connection from incoming clients. The clients is a array for the client that ever been connected. the db is the where data stored, who has Put()
, Get()
, Delete()
, Update()
functions to operates data. connections
is a chan that convey the coming connection that listener accepts to the go routine (workLoop) clients[]
stores. count_active
is the chan that return the count result from the workLoop to the CountActive
function return. The queries is a chan for queries processing. When the parseLoop finish a parse, and pass the query struct to the queries chan for the workLoop to handle later.
Query struct is used to describe what the task is from client.
type Query struct {
key string
value string
queryType string
client *Client
value_new string
}
The buffer chan in Client struct is used to store the return value for the Get()
function called by client.
type Client struct {
connection net.Conn
alive int
closed chan struct{} // Used to signal the closure of the client
buffer chan []byte // Buffered channel to queue messages
}
Part 2 Go Routines
After the Start()
function was called, the kvs starts with acceptClient() and workLoop() two Go routine start. Whenever a new connection establishes, the kvs.connections chan will be sent with a message and a new Client will born in the clients array. Meanwhile, the readFromClient()
and writeToClient()
Go routine will start. Whenever a request message is received by readFromClient()
, the parsed Query will be send to the kvs workLoop, waiting for process. The buffer chan is used to keep data.
To address the slow reader problem, using a condition for the buffer to send client data.
if len(query.client.buffer) < maxBufferSize {
query.client.buffer <- buffer.Bytes()
}
When a Close() is called, the close_signal chan will be close and all the Go routine with this chan selected, will be noticed and return the go routine.
The CountActive()
is simply a query to handle in the workLoop.
Eventually, the KV server is implemented.